package com.baomibing.flow.transfer;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomibing.core.exception.ServerRuntimeException;
import com.baomibing.flow.constant.ActionTypeEnum;
import com.baomibing.flow.constant.NodeTypeEnum;
import com.baomibing.flow.constant.ProcessTypeEnum;
import com.baomibing.flow.enginee.Process;
import com.baomibing.flow.enginee.*;
import com.baomibing.flow.exception.FlowExceptionEnum;
import com.baomibing.flow.model.*;
import com.baomibing.tool.util.Checker;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Getter;
import org.apache.commons.lang3.EnumUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * WorkFlowTransfer
 *
 * @author frog 2023/10/20 14:07
 * @version 1.0.0
 **/
public class WorkFlowTransfer {

    private final WorkFlow flow;

    private static final String NODE_TYPE = "nodeType";
    private static final String NAME = "name";
    private static final String SOURCE = "source";
    private static final String TARGET = "target";
    private static final String ACTION = "action";
    private static final String PROCESS_TYPE = "processType";
    private static final String NODE = "node";
    private static final String TARGET_PROCESS = "targetProcess";
    private static final String PROCESSES = "processes";

    @Getter
    private final Map<String, WorkNode> nodeMap = Maps.newHashMap();

    private void putIfNotExist(WorkNode workNode) {
        if (Checker.beNull(nodeMap.get(workNode.getName()))) {
            nodeMap.put(workNode.getName(), workNode);
        }
    }

    public WorkNode getNodeByName(String name) {
        return nodeMap.get(name);
    }

    private WorkNode getNodeDefaultIfNull(WorkNode workNode) {
        WorkNode node = nodeMap.get(workNode.getName());
        if (Checker.beNotNull(node)) {
            return node;
        }
        nodeMap.put(workNode.getName(), workNode);
        return workNode;
    }

    public WorkFlowTransfer(WorkFlow flow) {
        this.flow = flow;
    }

    private WorkNode handleWorkNode(JSONObject node) {
        NodeTypeEnum ntEnum = EnumUtils.getEnum(NodeTypeEnum.class, node.getString(NODE_TYPE));
        if (ntEnum == NodeTypeEnum.Start) {
            return  getNodeDefaultIfNull(new StartNode(node.getString(NAME)));//node.toJavaObject(StartNode.class);
        } else if (ntEnum == NodeTypeEnum.End) {
            return getNodeDefaultIfNull(new EndNode(node.getString(NAME)));//node.toJavaObject(EndNode.class);
        } else if (ntEnum == NodeTypeEnum.Fork) {
            return getNodeDefaultIfNull(new ForkNode(node.getString(NAME)));//node.toJavaObject(ForkNode.class);
        } else if (ntEnum == NodeTypeEnum.Join) {
            return getNodeDefaultIfNull(new JoinNode(node.getString(NAME)));//node.toJavaObject(JoinNode.class);
        } else if (ntEnum == NodeTypeEnum.Parallel) {
            return getNodeDefaultIfNull(new ParallelNode(node.getString(NAME)));//node.toJavaObject(ParallelNode.class);
        } else if (ntEnum == NodeTypeEnum.Task) {
            TaskNode tn = node.toJavaObject(TaskNode.class);
            return getNodeDefaultIfNull(tn);
        }
        throw new ServerRuntimeException(FlowExceptionEnum.NOT_SUPPORT_THE_TYPE_OF_WORK_NODE, ntEnum.name());
    }

    private void handleTaskNodeForAllAction(TaskNode tn ,WorkNode target) {
        if (tn.getActionType() == ActionTypeEnum.ALL) {
            List<SubProcess> joinTaskProcess = Lists.newArrayList();
            for (String s : tn.getEntrusts()) {
                TransitionNode t = new TransitionNode(tn, target, false, false);
                SubProcess sub = new SubProcess(flow).source(tn).actor(s).target(target).action(t);
                joinTaskProcess.add(sub);
            }
            TaskJoinProcess taskJoinProcess = new TaskJoinProcess(flow);
            taskJoinProcess.setProcesses(joinTaskProcess);
            tn.setChildProcess(taskJoinProcess);
        }
    }


    private SubProcess parseSubProcess(JSONObject subProcess) {
        WorkNode source = handleWorkNode(subProcess.getJSONObject(SOURCE));
        WorkNode target = handleWorkNode(subProcess.getJSONObject(TARGET));
        if (source instanceof TaskNode) {
            TaskNode tn = (TaskNode) source;
            handleTaskNodeForAllAction(tn, target);
            putIfNotExist(tn);
        }

        JSONObject tnJson = subProcess.getJSONObject(ACTION);
        TransitionNode tn = new TransitionNode(source, target);
        return new SubProcess(flow).source(source).target(target).action(tn);
    }

    private List<SubProcess> handleSubProcess(String subProcesses, WorkFlow flow) {
        List<SubProcess> list = Lists.newArrayList();
        JSONArray array = JSONArray.parseArray(subProcesses);
        for (int i = 0, len = array.size(); i < len; i++) {
            list.add(parseSubProcess(array.getJSONObject(i)));
        }
        return list;
    }

    private List<Process> handleProcess(String processes) {
        List<Process> list = Lists.newArrayList();
        JSONArray array = JSONArray.parseArray(processes);
        for (int i = array.size() -1; i >= 0; i--) {
            JSONObject json = array.getJSONObject(i);
            String pt = json.getString(PROCESS_TYPE);
            ProcessTypeEnum ptEnum = EnumUtils.getEnum(ProcessTypeEnum.class, pt);
            if (ptEnum == ProcessTypeEnum.SUB) {
                list.add(parseSubProcess(json));
            } else if (ptEnum == ProcessTypeEnum.FORK) {
                List<Process> forkInnerProcess = handleProcess(json.getString(PROCESSES));
                ForkNode fn = (ForkNode) handleWorkNode(json.getJSONObject(NODE));
                ForkProcess fp = new ForkProcess(flow, fn);
                fp.setProcesses(forkInnerProcess);
                list.add(fp);
            } else if (ptEnum == ProcessTypeEnum.JOIN) {
                List<SubProcess> joinInnerProcess = handleSubProcess(json.getString(PROCESSES), flow);
                SubProcess joinSubProcess = parseSubProcess(json.getJSONObject(TARGET_PROCESS));
                JoinProcess jp = new JoinProcess(flow);
                jp.setTargetProcess(joinSubProcess);
                jp.setProcesses(joinInnerProcess);
                list.add(jp);
            } else if (ptEnum == ProcessTypeEnum.PARALLEL) {
                List<Process> parallelInnerProcess =  handleProcess(json.getString(PROCESSES));
                ParallelProcess pp = new ParallelProcess(flow);
                WorkNode source = handleWorkNode(json.getJSONObject(SOURCE));
                pp.setProcesses(parallelInnerProcess);
                pp.source(source);
                list.add(pp);
            }
        }
        return list;
    }


    public List<Process> processDeserialize(String processes) {

        if (Checker.beEmpty(processes)) {
            return Lists.newArrayList();
        }
        List<Process> list =  handleProcess(processes);
        Collections.reverse(list);
        return list;

    }
}
